//
//  AliyunOSSManager.m
//  OSSDemo
//
//  Created by iOS on 2017/5/26.
//  Copyright © 2017年 MS. All rights reserved.
//

#import "AliyunOSSManager.h"
#import <AliyunOSSiOS/AliyunOSSiOS.h>

NSString *const AccessKey = @"STS.KGUv3hT9mQUVAn1iCZ5JTda4Q";
NSString *const SecretKey = @"5PDxGn3CJqEeAGE5VxaGRM8SsPCW55An5sf7VMvnasjk";
NSString *const endPoint = @"https://oss-cn-zhangjiakou.aliyuncs.com"; ///< 指明Bucket所在的Region域名，2017年以后苹果要求APP符合ATS政策，这里要写https的endpoint，如 "https://oss-cn-hangzhou.aliyuncs.com"
NSString *const multipartUploadKey = @"multipartUploadObject";

OSSClient * client;
static AliyunOSSManager *manager;

@implementation AliyunOSSManager

+ (instancetype)shareManager
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        manager = [[self alloc] init];
        [manager initOSSClient];
    });
    
    return manager;
}

/**
 设置环境
 */
- (void)setupEnvironment
{
    // 打开调试log
//    [OSSLog enableLog];
    
    // 在本地生成一些文件来演示
//    [self initLocalFile];
    
    // 初始化SDK
    [self initOSSClient];
}

- (NSString *)getDocumentDirectory
{
    NSString *path = NSHomeDirectory();
    NSLog(@"NSHomeDirectory:%@",path);
    NSString *userName = NSUserName();
    NSString *rootPath = NSHomeDirectoryForUser(userName);
    NSLog(@"NSHomeDirectoryForUser:%@",rootPath);
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString * documentsDirectory = [paths objectAtIndex:0];
    return documentsDirectory;
}

- (void)initLocalFile
{
    NSFileManager *fm = [NSFileManager defaultManager];
    NSString *mainDir = [self getDocumentDirectory];
    
    NSArray *fileNameArray = @[@"file1k", @"file10k", @"file100k", @"file1m", @"file10m", @"fileDirA/", @"fileDirB/"];
    NSArray *fileSizeArray = @[@1024, @10240, @102400, @1024000, @10240000, @1024, @1024];
    
    NSMutableData *basePart = [NSMutableData dataWithCapacity:1024];
    for (int i = 0; i < 1024/4; i++)
    {
        u_int32_t randomBit = arc4random();
        [basePart appendBytes:(void*)&randomBit length:4];
    }
    
    for (int i = 0; i < [fileNameArray count]; i++)
    {
        NSString *name = [fileNameArray objectAtIndex:i];
        long size = [[fileSizeArray objectAtIndex:i] longValue];
        NSString *newFilePath = [mainDir stringByAppendingPathComponent:name];
        if ([fm fileExistsAtPath:newFilePath]) {
            [fm removeItemAtPath:newFilePath error:nil];
        }
        [fm createFileAtPath:newFilePath contents:nil attributes:nil];
        NSFileHandle *f = [NSFileHandle fileHandleForWritingAtPath:newFilePath];
        for (int k = 0; k < size/1024; k++) {
            [f writeData:basePart];
        }
        [f closeFile];
    }
    
    NSLog(@"main bundle: %@", mainDir);
}

- (void)initOSSClient
{
    // 明文设置AccessKeyId/AccessKeySecret的方式建议只在测试时使用
//    id<OSSCredentialProvider> credential = [[OSSPlainTextAKSKPairCredentialProvider alloc] initWithPlainTextAccessKey:AccessKey secretKey:SecretKey];

    // STS鉴权模式
    id<OSSCredentialProvider> credential2 = [[OSSFederationCredentialProvider alloc] initWithFederationTokenGetter:^OSSFederationToken * {
        // 构造请求访问您的业务server
        NSURL * url = [NSURL URLWithString:@"http://www.minweixian.com/api/aliyun_oss_sts.php"];
        NSURLRequest * request = [NSURLRequest requestWithURL:url];
        OSSTaskCompletionSource * tcs = [OSSTaskCompletionSource taskCompletionSource];
        NSURLSession * session = [NSURLSession sharedSession];
        // 发送请求
        NSURLSessionTask * sessionTask = [session dataTaskWithRequest:request
                                                    completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
                                                        if (error) {
                                                            [tcs setError:error];
                                                            return;
                                                        }
                                                        [tcs setResult:data];
                                                    }];
        [sessionTask resume];
        // 需要阻塞等待请求返回
        [tcs.task waitUntilFinished];
        // 解析结果
        if (tcs.task.error) {
            NSLog(@"get token error: %@", tcs.task.error);
            return nil;
        } else {
            // 返回数据是json格式，需要解析得到token的各个字段
            NSDictionary *object = [NSJSONSerialization JSONObjectWithData:tcs.task.result
                                                                    options:kNilOptions
                                                                      error:nil];
            OSSFederationToken *token = [OSSFederationToken new];
            token.tAccessKey = [object objectForKey:@"AccessKeyId"];
            token.tSecretKey = [object objectForKey:@"AccessKeySecret"];
            token.tToken = [object objectForKey:@"SecurityToken"];
            token.expirationTimeInGMTFormat = [object objectForKey:@"Expiration"];
            NSLog(@"get token: %@", token);
            return token;
        }
    }];
    
    client = [[OSSClient alloc] initWithEndpoint:endPoint credentialProvider:credential2];
}

- (void)createBucket
{
    OSSCreateBucketRequest *create = [OSSCreateBucketRequest new];
    create.bucketName = @"ios_bucket";
    create.xOssACL = @"public-read-write";
    create.location = @"oss-cn-zhangjiakou-internal";
    
    OSSTask *createTask = [client createBucket:create];
    
    [createTask continueWithBlock:^id(OSSTask *task) {
        if (!task.error) {
            NSLog(@"create bucket success!");
        } else {
            NSLog(@"create bucket failed, error: %@", task.error);
        }
        return nil;
    }];
}

- (void)deleteBucket
{
    OSSDeleteBucketRequest *delete = [OSSDeleteBucketRequest new];
    delete.bucketName = @"<bucketName>";
    
    OSSTask *deleteTask = [client deleteBucket:delete];
    
    [deleteTask continueWithBlock:^id(OSSTask *task) {
        if (!task.error)
        {
            NSLog(@"delete bucket success!");
        }
        else
        {
            NSLog(@"delete bucket failed, error: %@", task.error);
        }
        return nil;
    }];
}

- (void)listObjectsInBucket
{
    OSSGetBucketRequest *getBucket = [OSSGetBucketRequest new];
    getBucket.bucketName = @"android-test";
    getBucket.delimiter = @"";
    getBucket.prefix = @"";
    
    OSSTask *getBucketTask = [client getBucket:getBucket];
    
    [getBucketTask continueWithBlock:^id(OSSTask *task) {
        if (!task.error)
        {
            OSSGetBucketResult * result = task.result;
            NSLog(@"get bucket success!");
            for (NSDictionary * objectInfo in result.contents)
            {
                NSLog(@"list object: %@", objectInfo);
            }
        }
        else
        {
            NSLog(@"get bucket failed, error: %@", task.error);
        }
        
        return nil;
    }];
}


- (void)uploadObjectAsync:(NSArray<UIImage *> *)images objectKeys:(NSArray<NSString *> *)objectKeys isAsync:(BOOL)isAsync complete:(void(^)(NSArray<UIImage *> * images, NSArray<NSString *> *objectKeys))complete
{
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = images.count;
    
    __block NSMutableArray *failtureImages = [[NSMutableArray alloc] init];
    __block NSMutableArray *failtureObjectKeys = [[NSMutableArray alloc] init];
    
    [images enumerateObjectsUsingBlock:^(UIImage * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
            OSSPutObjectRequest *put = [OSSPutObjectRequest new];
            
            // required fields
            put.bucketName = @"mwx-img";
            put.objectKey = objectKeys[idx];
            UIImage *img = obj;
            /**
             图片转data
             */
            //    NSData *imgData = UIImagePNGRepresentation(img);
            NSData *imgData = UIImageJPEGRepresentation(img, 0.3);
            put.uploadingData = imgData;
            
            // optional fields
            put.uploadProgress = ^(int64_t bytesSent, int64_t totalByteSent, int64_t totalBytesExpectedToSend) {
                NSLog(@"上传进度(%@)：%lld, %lld, %lld", objectKeys[idx], bytesSent, totalByteSent, totalBytesExpectedToSend);
            };
//            put.contentType = @"png";
//            put.contentMd5 = @"";
//            put.contentEncoding = @"";
//            put.contentDisposition = @"";
            
            OSSTask *putTask = [client putObject:put];
            // 阻塞直到上传完成
            [putTask waitUntilFinished];
            
            [putTask continueWithBlock:^id(OSSTask *task) {
                NSLog(@"objectKey: %@", put.objectKey);
                if (!task.error)
                {
                    NSLog(@"%@ upload object success!", put.objectKey);
                }
                else
                {
                    NSLog(@"upload object failed, error: %@" , task.error);
                    [failtureImages addObject:[UIImage imageWithData:put.uploadingData]];
                    [failtureObjectKeys addObject:put.objectKey];
                }
                
                return nil;
            }];
            
            if (isAsync)
            {
                if (obj == images.lastObject)
                {
                    if (complete)
                    {
                        complete(failtureImages, failtureObjectKeys);
                    }
                }
            }
        }];
        
        if (queue.operations.count != 0)
        {
            [operation addDependency:queue.operations.lastObject];
        }
        [queue addOperation:operation];
        
    }];
    
    if (!isAsync)
    {
        [queue waitUntilAllOperationsAreFinished];
        if (complete)
        {
            complete(failtureImages, failtureObjectKeys);
        }
    }
}


// 获取meta
- (void)headObject
{
    OSSHeadObjectRequest *head = [OSSHeadObjectRequest new];
    head.bucketName = @"android-test";
    head.objectKey = @"file1m";
    
    OSSTask *headTask = [client headObject:head];
    
    [headTask continueWithBlock:^id(OSSTask *task) {
        if (!task.error)
        {
            OSSHeadObjectResult * headResult = task.result;
            NSLog(@"all response header: %@", headResult.httpResponseHeaderFields);
            
            // some object properties include the 'x-oss-meta-*'s
            NSLog(@"head object result: %@", headResult.objectMeta);
        }
        else
        {
            NSLog(@"head object error: %@", task.error);
        }
        return nil;
    }];
}


// 签名URL授予第三方访问
- (void)signAccessObjectURL
{
    NSString *constrainURL = nil;
    NSString *publicURL = nil;
    
    // sign constrain url
    OSSTask *task = [client presignConstrainURLWithBucketName:@"<bucket name>"
                                                 withObjectKey:@"<object key>"
                                        withExpirationInterval:60 * 30];
    if (!task.error)
    {
        constrainURL = task.result;
    }
    else
    {
        NSLog(@"error: %@", task.error);
    }
    
    // sign public url
    task = [client presignPublicURLWithBucketName:@"<bucket name>"
                                    withObjectKey:@"<object key>"];
    if (!task.error)
    {
        publicURL = task.result;
    }
    else
    {
        NSLog(@"sign url error: %@", task.error);
    }
}

// 分块上传
- (void)multipartUpload
{
    
    __block NSString *uploadId = nil;
    __block NSMutableArray *partInfos = [NSMutableArray new];
    
    NSString *uploadToBucket = @"android-test";
    NSString *uploadObjectkey = @"file20m";
    
    OSSInitMultipartUploadRequest *init = [OSSInitMultipartUploadRequest new];
    init.bucketName = uploadToBucket;
    init.objectKey = uploadObjectkey;
    init.contentType = @"application/octet-stream";
    init.objectMeta = [NSMutableDictionary dictionaryWithObjectsAndKeys:@"value1", @"x-oss-meta-name1", nil];
    
    OSSTask *initTask = [client multipartUploadInit:init];
    
    [initTask waitUntilFinished];
    
    if (!initTask.error)
    {
        OSSInitMultipartUploadResult *result = initTask.result;
        uploadId = result.uploadId;
        NSLog(@"init multipart upload success: %@", result.uploadId);
    }
    else
    {
        NSLog(@"multipart upload failed, error: %@", initTask.error);
        return;
    }
    
    for (int i = 1; i <= 20; i++)
    {
        @autoreleasepool {
            OSSUploadPartRequest *uploadPart = [OSSUploadPartRequest new];
            uploadPart.bucketName = uploadToBucket;
            uploadPart.objectkey = uploadObjectkey;
            uploadPart.uploadId = uploadId;
            uploadPart.partNumber = i; // part number start from 1
            
            NSString * docDir = [self getDocumentDirectory];
            // uploadPart.uploadPartFileURL = [NSURL URLWithString:[docDir stringByAppendingPathComponent:@"file1m"]];
            uploadPart.uploadPartData = [NSData dataWithContentsOfFile:[docDir stringByAppendingPathComponent:@"file1m"]];
            
            OSSTask *uploadPartTask = [client uploadPart:uploadPart];
            
            [uploadPartTask waitUntilFinished];
            
            if (!uploadPartTask.error)
            {
                OSSUploadPartResult * result = uploadPartTask.result;
                uint64_t fileSize = [[[NSFileManager defaultManager] attributesOfItemAtPath:uploadPart.uploadPartFileURL.absoluteString error:nil] fileSize];
                [partInfos addObject:[OSSPartInfo partInfoWithPartNum:i eTag:result.eTag size:fileSize]];
            }
            else
            {
                NSLog(@"upload part error: %@", uploadPartTask.error);
                return;
            }
        }
    }
    
    OSSCompleteMultipartUploadRequest *complete = [OSSCompleteMultipartUploadRequest new];
    complete.bucketName = uploadToBucket;
    complete.objectKey = uploadObjectkey;
    complete.uploadId = uploadId;
    complete.partInfos = partInfos;
    
    OSSTask *completeTask = [client completeMultipartUpload:complete];
    
    [completeTask waitUntilFinished];
    
    if (!completeTask.error)
    {
        NSLog(@"multipart upload success!");
    }
    else
    {
        NSLog(@"multipart upload failed, error: %@", completeTask.error);
        return;
    }
}

// 罗列分块
- (void)listParts
{
    OSSListPartsRequest *listParts = [OSSListPartsRequest new];
    listParts.bucketName = @"android-test";
    listParts.objectKey = @"file3m";
    listParts.uploadId = @"265B84D863B64C80BA552959B8B207F0";
    
    OSSTask *listPartTask = [client listParts:listParts];
    
    [listPartTask continueWithBlock:^id(OSSTask *task) {
        if (!task.error)
        {
            NSLog(@"list part result success!");
            OSSListPartsResult *listPartResult = task.result;
            for (NSDictionary *partInfo in listPartResult.parts)
            {
                NSLog(@"each part: %@", partInfo);
            }
        }
        else
        {
            NSLog(@"list part result error: %@", task.error);
        }
        
        return nil;
    }];
}

// 断点续传
- (void)resumableUpload
{
    __block NSString *recordKey;
    
    NSString *docDir = [self getDocumentDirectory];
    NSString *filePath = [docDir stringByAppendingPathComponent:@"file10m"];
    NSString *bucketName = @"android-test";
    NSString *objectKey = @"uploadKey";
    
    [[[[[[OSSTask taskWithResult:nil] continueWithBlock:^id(OSSTask *task) {
        // 为该文件构造一个唯一的记录键
        NSURL *fileURL = [NSURL fileURLWithPath:filePath];
        NSDate *lastModified;
        NSError *error;
        
        [fileURL getResourceValue:&lastModified forKey:NSURLContentModificationDateKey error:&error];
        
        if (error)
        {
            return [OSSTask taskWithError:error];
        }
        
        recordKey = [NSString stringWithFormat:@"%@-%@-%@-%@", bucketName, objectKey, [OSSUtil getRelativePath:filePath], lastModified];
        // 通过记录键查看本地是否保存有未完成的UploadId
        NSUserDefaults * userDefault = [NSUserDefaults standardUserDefaults];
        return [OSSTask taskWithResult:[userDefault objectForKey:recordKey]];
    }] continueWithSuccessBlock:^id(OSSTask *task) {
        if (!task.result) {
            // 如果本地尚无记录，调用初始化UploadId接口获取
            OSSInitMultipartUploadRequest * initMultipart = [OSSInitMultipartUploadRequest new];
            initMultipart.bucketName = bucketName;
            initMultipart.objectKey = objectKey;
            initMultipart.contentType = @"application/octet-stream";
            
            return [client multipartUploadInit:initMultipart];
        }
        
        OSSLogVerbose(@"An resumable task for uploadid: %@", task.result);
        
        return task;
    }] continueWithSuccessBlock:^id(OSSTask *task) {
        NSString * uploadId = nil;
        
        if (task.error) {
            return task;
        }
        
        if ([task.result isKindOfClass:[OSSInitMultipartUploadResult class]])
        {
            uploadId = ((OSSInitMultipartUploadResult *)task.result).uploadId;
        }
        else
        {
            uploadId = task.result;
        }
        
        if (!uploadId)
        {
            return [OSSTask taskWithError:[NSError errorWithDomain:OSSClientErrorDomain
                                                              code:OSSClientErrorCodeNilUploadid
                                                          userInfo:@{OSSErrorMessageTOKEN: @"Can't get an upload id"}]];
        }
        // 将“记录键：UploadId”持久化到本地存储
        NSUserDefaults * userDefault = [NSUserDefaults standardUserDefaults];
        [userDefault setObject:uploadId forKey:recordKey];
        [userDefault synchronize];
        
        return [OSSTask taskWithResult:uploadId];
    }] continueWithSuccessBlock:^id(OSSTask *task) {
        // 持有UploadId上传文件
        OSSResumableUploadRequest * resumableUpload = [OSSResumableUploadRequest new];
        resumableUpload.bucketName = bucketName;
        resumableUpload.objectKey = objectKey;
        resumableUpload.uploadId = task.result;
        resumableUpload.uploadingFileURL = [NSURL fileURLWithPath:filePath];
        resumableUpload.uploadProgress = ^(int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend) {
            NSLog(@"%lld %lld %lld", bytesSent, totalBytesSent, totalBytesExpectedToSend);
        };
        return [client resumableUpload:resumableUpload];
    }] continueWithBlock:^id(OSSTask *task) {
        if (task.error)
        {
            if ([task.error.domain isEqualToString:OSSClientErrorDomain] && task.error.code == OSSClientErrorCodeCannotResumeUpload)
            {
                // 如果续传失败且无法恢复，需要删除本地记录的UploadId，然后重启任务
                [[NSUserDefaults standardUserDefaults] removeObjectForKey:recordKey];
            }
        }
        else
        {
            NSLog(@"upload completed!");
            // 上传成功，删除本地保存的UploadId
            [[NSUserDefaults standardUserDefaults] removeObjectForKey:recordKey];
        }
        
        return nil;
    }];
}

-(UIImage *)imageCompressForWidth:(UIImage *)sourceImage targetWidth:(CGFloat)defineWidth
{
    CGSize imageSize = sourceImage.size;
    CGFloat width = imageSize.width;
    CGFloat height = imageSize.height;
    CGFloat targetWidth = defineWidth;
    CGFloat targetHeight = (targetWidth / width) * height;
    UIGraphicsBeginImageContext(CGSizeMake(targetWidth, targetHeight));
    [sourceImage drawInRect:CGRectMake(0,0,targetWidth, targetHeight)];
    UIImage* newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return newImage;
}

@end
